// https://github.com/photopea/Typr.js

var Typr = {};

Typr["parse"] = function (buff) {
    var readFont = function (data, idx, offset, tmap) {
        var bin = Typr["B"];

        var T = Typr["T"];
        var prsr = {
            "cmap": T.cmap,
            "head": T.head,
            "hhea": T.hhea,
            "maxp": T.maxp,
            "hmtx": T.hmtx,
            "name": T.name,
            "OS/2": T.OS2,
            "post": T.post,

            "loca": T.loca,
            "kern": T.kern,
            "glyf": T.glyf,

            "CFF ": T.CFF,
            /*
            "GPOS",
            "GSUB",
            "GDEF",*/

            "SVG ": T.SVG
            //"VORG",
        };
        var obj = { "_data": data, "_index": idx, "_offset": offset };

        for (var t in prsr) {
            var tab = Typr["findTable"](data, t, offset);
            if (tab) {
                var off = tab[0], tobj = tmap[off];
                if (tobj == null) tobj = prsr[t].parseTab(data, off, tab[1], obj);
                obj[t] = tmap[off] = tobj;
            }
        }
        return obj;
    }


    var bin = Typr["B"];
    var data = new Uint8Array(buff);

    var tmap = {};
    var tag = bin.readASCII(data, 0, 4);
    if (tag == "ttcf") {
        var offset = 4;
        var majV = bin.readUshort(data, offset); offset += 2;
        var minV = bin.readUshort(data, offset); offset += 2;
        var numF = bin.readUint(data, offset); offset += 4;
        var fnts = [];
        for (var i = 0; i < numF; i++) {
            var foff = bin.readUint(data, offset); offset += 4;
            fnts.push(readFont(data, i, foff, tmap));
        }
        return fnts;
    }
    else return [readFont(data, 0, 0, tmap)];
}


Typr["findTable"] = function (data, tab, foff) {
    var bin = Typr["B"];
    var numTables = bin.readUshort(data, foff + 4);
    var offset = foff + 12;
    for (var i = 0; i < numTables; i++) {
        var tag = bin.readASCII(data, offset, 4);
        var checkSum = bin.readUint(data, offset + 4);
        var toffset = bin.readUint(data, offset + 8);
        var length = bin.readUint(data, offset + 12);
        if (tag == tab) return [toffset, length];
        offset += 16;
    }
    return null;
}
/*
Typr["splitBy"] = function(data,tag) {
    data = new Uint8Array(data);  console.log(data.slice(0,64));
    var bin = Typr["B"];
    var ttcf = bin.readASCII(data, 0, 4);  if(ttcf!="ttcf") return {};
	
    var offset = 8;
    var numF = bin.readUint  (data, offset);  offset+=4;
    var colls = [], used={};
    for(var i=0; i<numF; i++) {
        var foff = bin.readUint  (data, offset);  offset+=4;
        var toff = Typr["findTable"](data,tag,foff)[0];
        if(used[toff]==null) used[toff] = [];
        used[toff].push([foff,bin.readUshort(data,foff+4)]);  // font offset, numTables
    }
    for(var toff in used) {
        var offs = used[toff];
        var hlen = 12+4*offs.length;
        var out = new Uint8Array(hlen);		
        for(var i=0; i<8; i++) out[i]=data[i];
        bin.writeUint(out,8,offs.length);
    	
        for(var i=0; i<offs.length; i++) hlen += 12+offs[i][1]*16;
    	
        var hdrs = [out], tabs = [], hoff=out.length, toff=hlen, noffs={};
        for(var i=0; i<offs.length; i++) {
            bin.writeUint(out, 12+i*4, hoff);  hoff+=12+offs[i][1]*16;
            toff = Typr["_cutFont"](data, offs[i][0], hdrs, tabs, toff, noffs);
        }
        colls.push(Typr["_joinArrs"](hdrs.concat(tabs)));
    }
    return colls;
}

Typr["splitFonts"] = function(data) {
    data = new Uint8Array(data);
    var bin = Typr["B"];
    var ttcf = bin.readASCII(data, 0, 4);  if(ttcf!="ttcf") return {};
	
    var offset = 8;
    var numF = bin.readUint  (data, offset);  offset+=4;
    var fnts = [];
    for(var i=0; i<numF; i++) {
        var foff = bin.readUint  (data, offset);  offset+=4;
        fnts.push(Typr._cutFont(data, foff));
        break;
    }
    return fnts;
}

Typr["_cutFont"] = function(data,foff,hdrs,tabs,toff, noffs) {
    var bin = Typr["B"];
    var numTables = bin.readUshort(data, foff+4);
	
    var out = new Uint8Array(12+numTables*16);  hdrs.push(out);
    for(var i=0; i<12; i++) out[i]=data[foff+i];  //console.log(out);
	
    var off = 12;
    for(var i=0; i<numTables; i++)
    {
        var tag      = bin.readASCII(data, foff+off, 4); 
        var checkSum = bin.readUint (data, foff+off+ 4);
        var toffset  = bin.readUint (data, foff+off+ 8); 
        var length   = bin.readUint (data, foff+off+12);
    	
        while((length&3)!=0) length++;
    	
        for(var j=0; j<16; j++) out[off+j]=data[foff+off+j];
    	
        if(noffs[toffset]!=null) bin.writeUint(out,off+8,noffs[toffset]);
        else {
            noffs[toffset] = toff;
            bin.writeUint(out, off+8, toff);  
            tabs.push(new Uint8Array(data.buffer, toffset, length));  toff+=length;
        }
        off+=16;
    }
    return toff;
}
Typr["_joinArrs"] = function(tabs) {
    var len = 0;
    for(var i=0; i<tabs.length; i++) len+=tabs[i].length;
    var out = new Uint8Array(len), ooff=0;
    for(var i=0; i<tabs.length; i++) {
        var tab = tabs[i];
        for(var j=0; j<tab.length; j++) out[ooff+j]=tab[j];
        ooff+=tab.length;
    }
    return out;
}
*/

Typr["T"] = {};





Typr["B"] = {
    readFixed: function (data, o) {
        return ((data[o] << 8) | data[o + 1]) + (((data[o + 2] << 8) | data[o + 3]) / (256 * 256 + 4));
    },
    readF2dot14: function (data, o) {
        var num = Typr["B"].readShort(data, o);
        return num / 16384;
    },
    readInt: function (buff, p) {
        //if(p>=buff.length) throw "error";
        var a = Typr["B"].t.uint8;
        a[0] = buff[p + 3];
        a[1] = buff[p + 2];
        a[2] = buff[p + 1];
        a[3] = buff[p];
        return Typr["B"].t.int32[0];
    },

    readInt8: function (buff, p) {
        //if(p>=buff.length) throw "error";
        var a = Typr["B"].t.uint8;
        a[0] = buff[p];
        return Typr["B"].t.int8[0];
    },
    readShort: function (buff, p) {
        //if(p>=buff.length) throw "error";
        var a = Typr["B"].t.uint8;
        a[1] = buff[p]; a[0] = buff[p + 1];
        return Typr["B"].t.int16[0];
    },
    readUshort: function (buff, p) {
        //if(p>=buff.length) throw "error";
        return (buff[p] << 8) | buff[p + 1];
    },
    writeUshort: function (buff, p, n) {
        buff[p] = (n >> 8) & 255; buff[p + 1] = n & 255;
    },
    readUshorts: function (buff, p, len) {
        var arr = [];
        for (var i = 0; i < len; i++) {
            var v = Typr["B"].readUshort(buff, p + i * 2);  //if(v==932) console.log(p+i*2);
            arr.push(v);
        }
        return arr;
    },
    readUint: function (buff, p) {
        //if(p>=buff.length) throw "error";
        var a = Typr["B"].t.uint8;
        a[3] = buff[p]; a[2] = buff[p + 1]; a[1] = buff[p + 2]; a[0] = buff[p + 3];
        return Typr["B"].t.uint32[0];
    },
    writeUint: function (buff, p, n) {
        buff[p] = (n >> 24) & 255; buff[p + 1] = (n >> 16) & 255; buff[p + 2] = (n >> 8) & 255; buff[p + 3] = (n >> 0) & 255;
    },
    readUint64: function (buff, p) {
        //if(p>=buff.length) throw "error";
        return (Typr["B"].readUint(buff, p) * (0xffffffff + 1)) + Typr["B"].readUint(buff, p + 4);
    },
    readASCII: function (buff, p, l)	// l : length in Characters (not Bytes)
    {
        //if(p>=buff.length) throw "error";
        var s = "";
        for (var i = 0; i < l; i++) s += String.fromCharCode(buff[p + i]);
        return s;
    },
    writeASCII: function (buff, p, s)	// l : length in Characters (not Bytes)
    {
        for (var i = 0; i < s.length; i++)
            buff[p + i] = s.charCodeAt(i);
    },
    readUnicode: function (buff, p, l) {
        //if(p>=buff.length) throw "error";
        var s = "";
        for (var i = 0; i < l; i++) {
            var c = (buff[p++] << 8) | buff[p++];
            s += String.fromCharCode(c);
        }
        return s;
    },
    _tdec: null,
    readUTF8: function (buff, p, l) {
        var tdec = Typr["B"]._tdec;
        if (tdec && p == 0 && l == buff.length) return tdec["decode"](buff);
        return Typr["B"].readASCII(buff, p, l);
    },
    readBytes: function (buff, p, l) {
        //if(p>=buff.length) throw "error";
        var arr = [];
        for (var i = 0; i < l; i++) arr.push(buff[p + i]);
        return arr;
    },
    readASCIIArray: function (buff, p, l)	// l : length in Characters (not Bytes)
    {
        //if(p>=buff.length) throw "error";
        var s = [];
        for (var i = 0; i < l; i++)
            s.push(String.fromCharCode(buff[p + i]));
        return s;
    },
    t: function () {
        var ab = new ArrayBuffer(8);
        return {
            buff: ab,
            int8: new Int8Array(ab),
            uint8: new Uint8Array(ab),
            int16: new Int16Array(ab),
            uint16: new Uint16Array(ab),
            int32: new Int32Array(ab),
            uint32: new Uint32Array(ab)
        }
    }()
};






Typr["T"].CFF = {
    parseTab: function (data, offset, length) {
        var bin = Typr["B"];
        var CFF = Typr["T"].CFF;

        data = new Uint8Array(data.buffer, offset, length);
        offset = 0;

        // Header
        var major = data[offset]; offset++;
        var minor = data[offset]; offset++;
        var hdrSize = data[offset]; offset++;
        var offsize = data[offset]; offset++;
        //console.log(major, minor, hdrSize, offsize);

        // Name INDEX
        var ninds = [];
        offset = CFF.readIndex(data, offset, ninds);
        var names = [];

        for (var i = 0; i < ninds.length - 1; i++) names.push(bin.readASCII(data, offset + ninds[i], ninds[i + 1] - ninds[i]));
        offset += ninds[ninds.length - 1];


        // Top DICT INDEX
        var tdinds = [];
        offset = CFF.readIndex(data, offset, tdinds);  //console.log(tdinds);
        // Top DICT Data
        var topDicts = [];
        for (var i = 0; i < tdinds.length - 1; i++) topDicts.push(CFF.readDict(data, offset + tdinds[i], offset + tdinds[i + 1]));
        offset += tdinds[tdinds.length - 1];
        var topdict = topDicts[0];
        //console.log(topdict);

        // String INDEX
        var sinds = [];
        offset = CFF.readIndex(data, offset, sinds);
        // String Data
        var strings = [];
        for (var i = 0; i < sinds.length - 1; i++) strings.push(bin.readASCII(data, offset + sinds[i], sinds[i + 1] - sinds[i]));
        offset += sinds[sinds.length - 1];

        // Global Subr INDEX  (subroutines)		
        CFF.readSubrs(data, offset, topdict);

        // charstrings

        if (topdict["CharStrings"]) topdict["CharStrings"] = CFF.readBytes(data, topdict["CharStrings"]);

        // CID font
        if (topdict["ROS"]) {
            offset = topdict["FDArray"];
            var fdind = [];
            offset = CFF.readIndex(data, offset, fdind);

            topdict["FDArray"] = [];
            for (var i = 0; i < fdind.length - 1; i++) {
                var dict = CFF.readDict(data, offset + fdind[i], offset + fdind[i + 1]);
                CFF._readFDict(data, dict, strings);
                topdict["FDArray"].push(dict);
            }
            offset += fdind[fdind.length - 1];

            offset = topdict["FDSelect"];
            topdict["FDSelect"] = [];
            var fmt = data[offset]; offset++;
            if (fmt == 3) {
                var rns = bin.readUshort(data, offset); offset += 2;
                for (var i = 0; i < rns + 1; i++) {
                    topdict["FDSelect"].push(bin.readUshort(data, offset), data[offset + 2]); offset += 3;
                }
            }
            else throw fmt;
        }

        // Encoding
        //if(topdict["Encoding"]) topdict["Encoding"] = CFF.readEncoding(data, topdict["Encoding"], topdict["CharStrings"].length);

        // charset
        if (topdict["charset"]) topdict["charset"] = CFF.readCharset(data, topdict["charset"], topdict["CharStrings"].length);

        CFF._readFDict(data, topdict, strings);
        return topdict;
    },

    _readFDict: function (data, dict, ss) {
        var CFF = Typr["T"].CFF;
        var offset;
        if (dict["Private"]) {
            offset = dict["Private"][1];
            dict["Private"] = CFF.readDict(data, offset, offset + dict["Private"][0]);
            if (dict["Private"]["Subrs"]) CFF.readSubrs(data, offset + dict["Private"]["Subrs"], dict["Private"]);
        }
        for (var p in dict) if (["FamilyName", "FontName", "FullName", "Notice", "version", "Copyright"].indexOf(p) != -1) dict[p] = ss[dict[p] - 426 + 35];
    },

    readSubrs: function (data, offset, obj) {
        obj["Subrs"] = Typr["T"].CFF.readBytes(data, offset);

        var bias, nSubrs = obj["Subrs"].length + 1;
        if (false) bias = 0;
        else if (nSubrs < 1240) bias = 107;
        else if (nSubrs < 33900) bias = 1131;
        else bias = 32768;
        obj["Bias"] = bias;
    },
    readBytes: function (data, offset) {
        var bin = Typr["B"];
        var arr = [];
        offset = Typr["T"].CFF.readIndex(data, offset, arr);

        var subrs = [], arl = arr.length - 1, no = data.byteOffset + offset;
        for (var i = 0; i < arl; i++) {
            var ari = arr[i];
            subrs.push(new Uint8Array(data.buffer, no + ari, arr[i + 1] - ari));
        }
        return subrs;
    },

    tableSE: [
        0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0,
        1, 2, 3, 4, 5, 6, 7, 8,
        9, 10, 11, 12, 13, 14, 15, 16,
        17, 18, 19, 20, 21, 22, 23, 24,
        25, 26, 27, 28, 29, 30, 31, 32,
        33, 34, 35, 36, 37, 38, 39, 40,
        41, 42, 43, 44, 45, 46, 47, 48,
        49, 50, 51, 52, 53, 54, 55, 56,
        57, 58, 59, 60, 61, 62, 63, 64,
        65, 66, 67, 68, 69, 70, 71, 72,
        73, 74, 75, 76, 77, 78, 79, 80,
        81, 82, 83, 84, 85, 86, 87, 88,
        89, 90, 91, 92, 93, 94, 95, 0,
        0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0,
        0, 96, 97, 98, 99, 100, 101, 102,
        103, 104, 105, 106, 107, 108, 109, 110,
        0, 111, 112, 113, 114, 0, 115, 116,
        117, 118, 119, 120, 121, 122, 0, 123,
        0, 124, 125, 126, 127, 128, 129, 130,
        131, 0, 132, 133, 0, 134, 135, 136,
        137, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0,
        0, 138, 0, 139, 0, 0, 0, 0,
        140, 141, 142, 143, 0, 0, 0, 0,
        0, 144, 0, 0, 0, 145, 0, 0,
        146, 147, 148, 149, 0, 0, 0, 0
    ],

    glyphByUnicode: function (cff, code) {
        for (var i = 0; i < cff["charset"].length; i++) if (cff["charset"][i] == code) return i;
        return -1;
    },

    glyphBySE: function (cff, charcode)	// glyph by standard encoding
    {
        if (charcode < 0 || charcode > 255) return -1;
        return Typr["T"].CFF.glyphByUnicode(cff, Typr["T"].CFF.tableSE[charcode]);
    },

    /*readEncoding : function(data, offset, num)
    {
        var bin = Typr["B"];
    	
        var array = ['.notdef'];
        var format = data[offset];  offset++;
        //console.log("Encoding");
        //console.log(format);
    	
        if(format==0)
        {
            var nCodes = data[offset];  offset++;
            for(var i=0; i<nCodes; i++)  array.push(data[offset+i]);
        }
        /*
        else if(format==1 || format==2)
        {
            while(charset.length<num)
            {
                var first = bin.readUshort(data, offset);  offset+=2;
                var nLeft=0;
                if(format==1) {  nLeft = data[offset];  offset++;  }
                else          {  nLeft = bin.readUshort(data, offset);  offset+=2;  }
                for(var i=0; i<=nLeft; i++)  {  charset.push(first);  first++;  }
            }
        }
    	
        else throw "error: unknown encoding format: " + format;
    	
        return array;
    },*/

    readCharset: function (data, offset, num) {
        var bin = Typr["B"];

        var charset = ['.notdef'];
        var format = data[offset]; offset++;

        if (format == 0) {
            for (var i = 0; i < num; i++) {
                var first = bin.readUshort(data, offset); offset += 2;
                charset.push(first);
            }
        }
        else if (format == 1 || format == 2) {
            while (charset.length < num) {
                var first = bin.readUshort(data, offset); offset += 2;
                var nLeft = 0;
                if (format == 1) { nLeft = data[offset]; offset++; }
                else { nLeft = bin.readUshort(data, offset); offset += 2; }
                for (var i = 0; i <= nLeft; i++) { charset.push(first); first++; }
            }
        }
        else throw "error: format: " + format;

        return charset;
    },

    readIndex: function (data, offset, inds) {
        var bin = Typr["B"];

        var count = bin.readUshort(data, offset) + 1; offset += 2;
        var offsize = data[offset]; offset++;

        if (offsize == 1) for (var i = 0; i < count; i++) inds.push(data[offset + i]);
        else if (offsize == 2) for (var i = 0; i < count; i++) inds.push(bin.readUshort(data, offset + i * 2));
        else if (offsize == 3) for (var i = 0; i < count; i++) inds.push(bin.readUint(data, offset + i * 3 - 1) & 0x00ffffff);
        else if (offsize == 4) for (var i = 0; i < count; i++) inds.push(bin.readUint(data, offset + i * 4));
        else if (count != 1) throw "unsupported offset size: " + offsize + ", count: " + count;

        offset += count * offsize;
        return offset - 1;
    },

    getCharString: function (data, offset, o) {
        var bin = Typr["B"];

        var b0 = data[offset], b1 = data[offset + 1], b2 = data[offset + 2], b3 = data[offset + 3], b4 = data[offset + 4];
        var vs = 1;
        var op = null, val = null;
        // operand
        if (b0 <= 20) { op = b0; vs = 1; }
        if (b0 == 12) { op = b0 * 100 + b1; vs = 2; }
        //if(b0==19 || b0==20) { op = b0/*+" "+b1*/;  vs=2; }
        if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; }
        if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; }
        if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; }
        if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; }
        if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; }
        if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; }
        if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; }

        o.val = val != null ? val : "o" + op;
        o.size = vs;
    },

    readCharString: function (data, offset, length) {
        var end = offset + length;
        var bin = Typr["B"];
        var arr = [];

        while (offset < end) {
            var b0 = data[offset], b1 = data[offset + 1], b2 = data[offset + 2], b3 = data[offset + 3], b4 = data[offset + 4];
            var vs = 1;
            var op = null, val = null;
            // operand
            if (b0 <= 20) { op = b0; vs = 1; }
            if (b0 == 12) { op = b0 * 100 + b1; vs = 2; }
            if (b0 == 19 || b0 == 20) { op = b0/*+" "+b1*/; vs = 2; }
            if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; }
            if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; }
            if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; }
            if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; }
            if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; }
            if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; }
            if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; }

            arr.push(val != null ? val : "o" + op);
            offset += vs;

            //var cv = arr[arr.length-1];
            //if(cv==undefined) throw "error";
            //console.log()
        }
        return arr;
    },

    readDict: function (data, offset, end) {
        var bin = Typr["B"];
        //var dict = [];
        var dict = {};
        var carr = [];

        while (offset < end) {
            var b0 = data[offset], b1 = data[offset + 1], b2 = data[offset + 2], b3 = data[offset + 3], b4 = data[offset + 4];
            var vs = 1;
            var key = null, val = null;
            // operand
            if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; }
            if (b0 == 29) { val = bin.readInt(data, offset + 1); vs = 5; }
            if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; }
            if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; }
            if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; }
            if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; throw "unknown number"; }

            if (b0 == 30) {
                var nibs = [];
                vs = 1;
                while (true) {
                    var b = data[offset + vs]; vs++;
                    var nib0 = b >> 4, nib1 = b & 0xf;
                    if (nib0 != 0xf) nibs.push(nib0); if (nib1 != 0xf) nibs.push(nib1);
                    if (nib1 == 0xf) break;
                }
                var s = "";
                var chars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ".", "e", "e-", "reserved", "-", "endOfNumber"];
                for (var i = 0; i < nibs.length; i++) s += chars[nibs[i]];
                //console.log(nibs);
                val = parseFloat(s);
            }

            if (b0 <= 21)	// operator
            {
                var keys = ["version", "Notice", "FullName", "FamilyName", "Weight", "FontBBox", "BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues",
                    "StdHW", "StdVW", "escape", "UniqueID", "XUID", "charset", "Encoding", "CharStrings", "Private", "Subrs",
                    "defaultWidthX", "nominalWidthX"];

                key = keys[b0]; vs = 1;
                if (b0 == 12) {
                    var keys = ["Copyright", "isFixedPitch", "ItalicAngle", "UnderlinePosition", "UnderlineThickness", "PaintType", "CharstringType", "FontMatrix", "StrokeWidth", "BlueScale",
                        "BlueShift", "BlueFuzz", "StemSnapH", "StemSnapV", "ForceBold", "", "", "LanguageGroup", "ExpansionFactor", "initialRandomSeed",
                        "SyntheticBase", "PostScript", "BaseFontName", "BaseFontBlend", "", "", "", "", "", "",
                        "ROS", "CIDFontVersion", "CIDFontRevision", "CIDFontType", "CIDCount", "UIDBase", "FDArray", "FDSelect", "FontName"];
                    key = keys[b1]; vs = 2;
                }
            }

            if (key != null) { dict[key] = carr.length == 1 ? carr[0] : carr; carr = []; }
            else carr.push(val);

            offset += vs;
        }
        return dict;
    }
};


Typr["T"].cmap = {
    parseTab: function (data, offset, length) {
        var obj = { tables: [], ids: {}, off: offset };
        data = new Uint8Array(data.buffer, offset, length);
        offset = 0;

        var offset0 = offset;
        var bin = Typr["B"], rU = bin.readUshort, cmap = Typr["T"].cmap;
        var version = rU(data, offset); offset += 2;
        var numTables = rU(data, offset); offset += 2;

        //console.log(version, numTables);

        var offs = [];


        for (var i = 0; i < numTables; i++) {
            var platformID = rU(data, offset); offset += 2;
            var encodingID = rU(data, offset); offset += 2;
            var noffset = bin.readUint(data, offset); offset += 4;

            var id = "p" + platformID + "e" + encodingID;

            //console.log("cmap subtable", platformID, encodingID, noffset);


            var tind = offs.indexOf(noffset);

            if (tind == -1) {
                tind = obj.tables.length;
                var subt = {};
                offs.push(noffset);
                //var time = Date.now();
                var format = subt.format = rU(data, noffset);
                if (format == 0) subt = cmap.parse0(data, noffset, subt);
                //else if(format== 2) subt.off = noffset;
                else if (format == 4) subt = cmap.parse4(data, noffset, subt);
                else if (format == 6) subt = cmap.parse6(data, noffset, subt);
                else if (format == 12) subt = cmap.parse12(data, noffset, subt);
                //console.log(format, Date.now()-time);
                //else console.log("unknown format: "+format, platformID, encodingID, noffset);
                obj.tables.push(subt);
            }

            if (obj.ids[id] != null) throw "multiple tables for one platform+encoding";
            obj.ids[id] = tind;
        }
        return obj;
    },

    parse0: function (data, offset, obj) {
        var bin = Typr["B"];
        offset += 2;
        var len = bin.readUshort(data, offset); offset += 2;
        var lang = bin.readUshort(data, offset); offset += 2;
        obj.map = [];
        for (var i = 0; i < len - 6; i++) obj.map.push(data[offset + i]);
        return obj;
    },

    parse4: function (data, offset, obj) {
        var bin = Typr["B"], rU = bin.readUshort, rUs = bin.readUshorts;
        var offset0 = offset;
        offset += 2;
        var length = rU(data, offset); offset += 2;
        var language = rU(data, offset); offset += 2;
        var segCountX2 = rU(data, offset); offset += 2;
        var segCount = segCountX2 >>> 1;
        obj.searchRange = rU(data, offset); offset += 2;
        obj.entrySelector = rU(data, offset); offset += 2;
        obj.rangeShift = rU(data, offset); offset += 2;
        obj.endCount = rUs(data, offset, segCount); offset += segCount * 2;
        offset += 2;
        obj.startCount = rUs(data, offset, segCount); offset += segCount * 2;
        obj.idDelta = [];
        for (var i = 0; i < segCount; i++) { obj.idDelta.push(bin.readShort(data, offset)); offset += 2; }
        obj.idRangeOffset = rUs(data, offset, segCount); offset += segCount * 2;
        obj.glyphIdArray = rUs(data, offset, ((offset0 + length) - offset) >>> 1);  //offset += segCount*2;
        return obj;
    },

    parse6: function (data, offset, obj) {
        var bin = Typr["B"];
        var offset0 = offset;
        offset += 2;
        var length = bin.readUshort(data, offset); offset += 2;
        var language = bin.readUshort(data, offset); offset += 2;
        obj.firstCode = bin.readUshort(data, offset); offset += 2;
        var entryCount = bin.readUshort(data, offset); offset += 2;
        obj.glyphIdArray = [];
        for (var i = 0; i < entryCount; i++) { obj.glyphIdArray.push(bin.readUshort(data, offset)); offset += 2; }

        return obj;
    },

    parse12: function (data, offset, obj) {
        var bin = Typr["B"], rU = bin.readUint;
        var offset0 = offset;
        offset += 4;
        var length = rU(data, offset); offset += 4;
        var lang = rU(data, offset); offset += 4;
        var nGroups = rU(data, offset) * 3; offset += 4;

        var gps = obj.groups = new Uint32Array(nGroups);//new Uint32Array(data.slice(offset, offset+nGroups*12).buffer);  

        for (var i = 0; i < nGroups; i += 3) {
            gps[i] = rU(data, offset + (i << 2));
            gps[i + 1] = rU(data, offset + (i << 2) + 4);
            gps[i + 2] = rU(data, offset + (i << 2) + 8);
        }
        return obj;
    }
};

Typr["T"].glyf = {
    parseTab: function (data, offset, length, font) {
        var obj = [], ng = font["maxp"]["numGlyphs"];
        for (var g = 0; g < ng; g++) obj.push(null);
        return obj;
    },

    _parseGlyf: function (font, g) {
        var bin = Typr["B"];
        var data = font["_data"], loca = font["loca"];

        if (loca[g] == loca[g + 1]) return null;

        var offset = Typr["findTable"](data, "glyf", font["_offset"])[0] + loca[g];

        var gl = {};

        gl.noc = bin.readShort(data, offset); offset += 2;		// number of contours
        gl.xMin = bin.readShort(data, offset); offset += 2;
        gl.yMin = bin.readShort(data, offset); offset += 2;
        gl.xMax = bin.readShort(data, offset); offset += 2;
        gl.yMax = bin.readShort(data, offset); offset += 2;

        if (gl.xMin >= gl.xMax || gl.yMin >= gl.yMax) return null;

        if (gl.noc > 0) {
            gl.endPts = [];
            for (var i = 0; i < gl.noc; i++) { gl.endPts.push(bin.readUshort(data, offset)); offset += 2; }

            var instructionLength = bin.readUshort(data, offset); offset += 2;
            if ((data.length - offset) < instructionLength) return null;
            gl.instructions = bin.readBytes(data, offset, instructionLength); offset += instructionLength;

            var crdnum = gl.endPts[gl.noc - 1] + 1;
            gl.flags = [];
            for (var i = 0; i < crdnum; i++) {
                var flag = data[offset]; offset++;
                gl.flags.push(flag);
                if ((flag & 8) != 0) {
                    var rep = data[offset]; offset++;
                    for (var j = 0; j < rep; j++) { gl.flags.push(flag); i++; }
                }
            }
            gl.xs = [];
            for (var i = 0; i < crdnum; i++) {
                var i8 = ((gl.flags[i] & 2) != 0), same = ((gl.flags[i] & 16) != 0);
                if (i8) { gl.xs.push(same ? data[offset] : -data[offset]); offset++; }
                else {
                    if (same) gl.xs.push(0);
                    else { gl.xs.push(bin.readShort(data, offset)); offset += 2; }
                }
            }
            gl.ys = [];
            for (var i = 0; i < crdnum; i++) {
                var i8 = ((gl.flags[i] & 4) != 0), same = ((gl.flags[i] & 32) != 0);
                if (i8) { gl.ys.push(same ? data[offset] : -data[offset]); offset++; }
                else {
                    if (same) gl.ys.push(0);
                    else { gl.ys.push(bin.readShort(data, offset)); offset += 2; }
                }
            }
            var x = 0, y = 0;
            for (var i = 0; i < crdnum; i++) { x += gl.xs[i]; y += gl.ys[i]; gl.xs[i] = x; gl.ys[i] = y; }
            //console.log(endPtsOfContours, instructionLength, instructions, flags, xCoordinates, yCoordinates);
        }
        else {
            var ARG_1_AND_2_ARE_WORDS = 1 << 0;
            var ARGS_ARE_XY_VALUES = 1 << 1;
            var ROUND_XY_TO_GRID = 1 << 2;
            var WE_HAVE_A_SCALE = 1 << 3;
            var RESERVED = 1 << 4;
            var MORE_COMPONENTS = 1 << 5;
            var WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
            var WE_HAVE_A_TWO_BY_TWO = 1 << 7;
            var WE_HAVE_INSTRUCTIONS = 1 << 8;
            var USE_MY_METRICS = 1 << 9;
            var OVERLAP_COMPOUND = 1 << 10;
            var SCALED_COMPONENT_OFFSET = 1 << 11;
            var UNSCALED_COMPONENT_OFFSET = 1 << 12;

            gl.parts = [];
            var flags;
            do {
                flags = bin.readUshort(data, offset); offset += 2;
                var part = { m: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 }, p1: -1, p2: -1 }; gl.parts.push(part);
                part.glyphIndex = bin.readUshort(data, offset); offset += 2;
                if (flags & ARG_1_AND_2_ARE_WORDS) {
                    var arg1 = bin.readShort(data, offset); offset += 2;
                    var arg2 = bin.readShort(data, offset); offset += 2;
                } else {
                    var arg1 = bin.readInt8(data, offset); offset++;
                    var arg2 = bin.readInt8(data, offset); offset++;
                }

                if (flags & ARGS_ARE_XY_VALUES) { part.m.tx = arg1; part.m.ty = arg2; }
                else { part.p1 = arg1; part.p2 = arg2; }
                //part.m.tx = arg1;  part.m.ty = arg2;
                //else { throw "params are not XY values"; }

                if (flags & WE_HAVE_A_SCALE) {
                    part.m.a = part.m.d = bin.readF2dot14(data, offset); offset += 2;
                } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
                    part.m.a = bin.readF2dot14(data, offset); offset += 2;
                    part.m.d = bin.readF2dot14(data, offset); offset += 2;
                } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
                    part.m.a = bin.readF2dot14(data, offset); offset += 2;
                    part.m.b = bin.readF2dot14(data, offset); offset += 2;
                    part.m.c = bin.readF2dot14(data, offset); offset += 2;
                    part.m.d = bin.readF2dot14(data, offset); offset += 2;
                }
            } while (flags & MORE_COMPONENTS)
            if (flags & WE_HAVE_INSTRUCTIONS) {
                var numInstr = bin.readUshort(data, offset); offset += 2;
                gl.instr = [];
                for (var i = 0; i < numInstr; i++) { gl.instr.push(data[offset]); offset++; }
            }
        }
        return gl;
    }
};

Typr["T"].head = {
    parseTab: function (data, offset, length) {
        var bin = Typr["B"];
        var obj = {};
        var tableVersion = bin.readFixed(data, offset); offset += 4;

        obj["fontRevision"] = bin.readFixed(data, offset); offset += 4;
        var checkSumAdjustment = bin.readUint(data, offset); offset += 4;
        var magicNumber = bin.readUint(data, offset); offset += 4;
        obj["flags"] = bin.readUshort(data, offset); offset += 2;
        obj["unitsPerEm"] = bin.readUshort(data, offset); offset += 2;
        obj["created"] = bin.readUint64(data, offset); offset += 8;
        obj["modified"] = bin.readUint64(data, offset); offset += 8;
        obj["xMin"] = bin.readShort(data, offset); offset += 2;
        obj["yMin"] = bin.readShort(data, offset); offset += 2;
        obj["xMax"] = bin.readShort(data, offset); offset += 2;
        obj["yMax"] = bin.readShort(data, offset); offset += 2;
        obj["macStyle"] = bin.readUshort(data, offset); offset += 2;
        obj["lowestRecPPEM"] = bin.readUshort(data, offset); offset += 2;
        obj["fontDirectionHint"] = bin.readShort(data, offset); offset += 2;
        obj["indexToLocFormat"] = bin.readShort(data, offset); offset += 2;
        obj["glyphDataFormat"] = bin.readShort(data, offset); offset += 2;
        return obj;
    }
};

Typr["T"].hhea = {
    parseTab: function (data, offset, length) {
        var bin = Typr["B"];
        var obj = {};
        var tableVersion = bin.readFixed(data, offset); offset += 4;

        var keys = ["ascender", "descender", "lineGap",
            "advanceWidthMax", "minLeftSideBearing", "minRightSideBearing", "xMaxExtent",
            "caretSlopeRise", "caretSlopeRun", "caretOffset",
            "res0", "res1", "res2", "res3",
            "metricDataFormat", "numberOfHMetrics"];

        for (var i = 0; i < keys.length; i++) {
            var key = keys[i];
            var func = (key == "advanceWidthMax" || key == "numberOfHMetrics") ? bin.readUshort : bin.readShort;
            obj[key] = func(data, offset + i * 2);
        }
        return obj;
    }
};


Typr["T"].hmtx = {
    parseTab: function (data, offset, length, font) {
        var bin = Typr["B"];
        var aWidth = [];
        var lsBearing = [];

        var nG = font["maxp"]["numGlyphs"], nH = font["hhea"]["numberOfHMetrics"];
        var aw = 0, lsb = 0, i = 0;
        while (i < nH) { aw = bin.readUshort(data, offset + (i << 2)); lsb = bin.readShort(data, offset + (i << 2) + 2); aWidth.push(aw); lsBearing.push(lsb); i++; }
        while (i < nG) { aWidth.push(aw); lsBearing.push(lsb); i++; }

        return { aWidth: aWidth, lsBearing: lsBearing };
    }
};


Typr["T"].kern = {
    parseTab: function (data, offset, length, font) {
        var bin = Typr["B"], kern = Typr["T"].kern;

        var version = bin.readUshort(data, offset);
        if (version == 1) return kern.parseV1(data, offset, length, font);
        var nTables = bin.readUshort(data, offset + 2); offset += 4;

        var map = { glyph1: [], rval: [] };
        for (var i = 0; i < nTables; i++) {
            offset += 2;	// skip version
            var length = bin.readUshort(data, offset); offset += 2;
            var coverage = bin.readUshort(data, offset); offset += 2;
            var format = coverage >>> 8;
			/* I have seen format 128 once, that's why I do */ format &= 0xf;
            if (format == 0) offset = kern.readFormat0(data, offset, map);
            //else throw "unknown kern table format: "+format;
        }
        return map;
    },

    parseV1: function (data, offset, length, font) {
        var bin = Typr["B"], kern = Typr["T"].kern;

        var version = bin.readFixed(data, offset);   // 0x00010000 
        var nTables = bin.readUint(data, offset + 4); offset += 8;

        var map = { glyph1: [], rval: [] };
        for (var i = 0; i < nTables; i++) {
            var length = bin.readUint(data, offset); offset += 4;
            var coverage = bin.readUshort(data, offset); offset += 2;
            var tupleIndex = bin.readUshort(data, offset); offset += 2;
            var format = coverage & 0xff;
            if (format == 0) offset = kern.readFormat0(data, offset, map);
            //else throw "unknown kern table format: "+format;
        }
        return map;
    },

    readFormat0: function (data, offset, map) {
        var bin = Typr["B"], rUs = bin.readUshort;
        var pleft = -1;
        var nPairs = rUs(data, offset);
        var searchRange = rUs(data, offset + 2);
        var entrySelector = rUs(data, offset + 4);
        var rangeShift = rUs(data, offset + 6); offset += 8;
        for (var j = 0; j < nPairs; j++) {
            var left = rUs(data, offset); offset += 2;
            var right = rUs(data, offset); offset += 2;
            var value = bin.readShort(data, offset); offset += 2;
            if (left != pleft) { map.glyph1.push(left); map.rval.push({ glyph2: [], vals: [] }) }
            var rval = map.rval[map.rval.length - 1];
            rval.glyph2.push(right); rval.vals.push(value);
            pleft = left;
        }
        return offset;
    }
};


Typr["T"].loca = {
    parseTab: function (data, offset, length, font) {
        var bin = Typr["B"];
        var obj = [];

        var ver = font["head"]["indexToLocFormat"];
        var len = font["maxp"]["numGlyphs"] + 1;

        if (ver == 0) for (var i = 0; i < len; i++) obj.push(bin.readUshort(data, offset + (i << 1)) << 1);
        if (ver == 1) for (var i = 0; i < len; i++) obj.push(bin.readUint(data, offset + (i << 2)));

        return obj;
    }
};


Typr["T"].maxp = {
    parseTab: function (data, offset, length) {
        //console.log(data.length, offset, length);

        var bin = Typr["B"], rU = bin.readUshort;
        var obj = {};

        // both versions 0.5 and 1.0
        var ver = bin.readUint(data, offset); offset += 4;

        obj["numGlyphs"] = rU(data, offset); offset += 2;

        // only 1.0
        /*
        if(ver == 0x00010000) {
            obj.maxPoints             = rU(data, offset);  offset += 2;
            obj.maxContours           = rU(data, offset);  offset += 2;
            obj.maxCompositePoints    = rU(data, offset);  offset += 2;
            obj.maxCompositeContours  = rU(data, offset);  offset += 2;
            obj.maxZones              = rU(data, offset);  offset += 2;
            obj.maxTwilightPoints     = rU(data, offset);  offset += 2;
            obj.maxStorage            = rU(data, offset);  offset += 2;
            obj.maxFunctionDefs       = rU(data, offset);  offset += 2;
            obj.maxInstructionDefs    = rU(data, offset);  offset += 2;
            obj.maxStackElements      = rU(data, offset);  offset += 2;
            obj.maxSizeOfInstructions = rU(data, offset);  offset += 2;
            obj.maxComponentElements  = rU(data, offset);  offset += 2;
            obj.maxComponentDepth     = rU(data, offset);  offset += 2;
        }
        */

        return obj;
    }
};


Typr["T"].name = {
    parseTab: function (data, offset, length) {
        var bin = Typr["B"];
        var obj = {};
        var format = bin.readUshort(data, offset); offset += 2;
        var count = bin.readUshort(data, offset); offset += 2;
        var stringOffset = bin.readUshort(data, offset); offset += 2;

        //console.log(format,count);

        var names = [
            "copyright",
            "fontFamily",
            "fontSubfamily",
            "ID",
            "fullName",
            "version",
            "postScriptName",
            "trademark",
            "manufacturer",
            "designer",
            "description",
            "urlVendor",
            "urlDesigner",
            "licence",
            "licenceURL",
            "---",
            "typoFamilyName",
            "typoSubfamilyName",
            "compatibleFull",
            "sampleText",
            "postScriptCID",
            "wwsFamilyName",
            "wwsSubfamilyName",
            "lightPalette",
            "darkPalette"
        ];

        var offset0 = offset;
        var rU = bin.readUshort;

        for (var i = 0; i < count; i++) {
            var platformID = rU(data, offset); offset += 2;
            var encodingID = rU(data, offset); offset += 2;
            var languageID = rU(data, offset); offset += 2;
            var nameID = rU(data, offset); offset += 2;
            var slen = rU(data, offset); offset += 2;
            var noffset = rU(data, offset); offset += 2;
            //console.log(platformID, encodingID, languageID.toString(16), nameID, length, noffset);


            var soff = offset0 + count * 12 + noffset;
            var str;
            if (false) { }
            else if (platformID == 0) str = bin.readUnicode(data, soff, slen / 2);
            else if (platformID == 3 && encodingID == 0) str = bin.readUnicode(data, soff, slen / 2);
            else if (encodingID == 0) str = bin.readASCII(data, soff, slen);
            else if (encodingID == 1) str = bin.readUnicode(data, soff, slen / 2);
            else if (encodingID == 3) str = bin.readUnicode(data, soff, slen / 2);
            else if (encodingID == 4) str = bin.readUnicode(data, soff, slen / 2);
            else if (encodingID == 10) str = bin.readUnicode(data, soff, slen / 2);

            else if (platformID == 1) { str = bin.readASCII(data, soff, slen); console.log("reading unknown MAC encoding " + encodingID + " as ASCII") }
            else {
                console.log("unknown encoding " + encodingID + ", platformID: " + platformID);
                str = bin.readASCII(data, soff, slen);
            }

            var tid = "p" + platformID + "," + (languageID).toString(16);//Typr._platforms[platformID];
            if (obj[tid] == null) obj[tid] = {};
            obj[tid][names[nameID]] = str;
            obj[tid]["_lang"] = languageID;
            //console.log(tid, obj[tid]);
        }
        /*
        if(format == 1)
        {
            var langTagCount = bin.readUshort(data, offset);  offset += 2;
            for(var i=0; i<langTagCount; i++)
            {
                var length  = bin.readUshort(data, offset);  offset += 2;
                var noffset = bin.readUshort(data, offset);  offset += 2;
            }
        }
        */

        //console.log(obj);
        var psn = "postScriptName";

        for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0409) return obj[p];		// United States
        for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0000) return obj[p];		// Universal
        for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0c0c) return obj[p];		// Canada
        for (var p in obj) if (obj[p][psn] != null) return obj[p];

        var out;
        for (var p in obj) { out = obj[p]; break; }
        console.log("returning name table with languageID " + out._lang);
        if (out[psn] == null && out["ID"] != null) out[psn] = out["ID"];
        return out;
    }
}

Typr["T"].OS2 = {
    parseTab: function (data, offset, length) {
        var bin = Typr["B"];
        var ver = bin.readUshort(data, offset); offset += 2;

        var OS2 = Typr["T"].OS2;

        var obj = {};
        if (ver == 0) OS2.version0(data, offset, obj);
        else if (ver == 1) OS2.version1(data, offset, obj);
        else if (ver == 2 || ver == 3 || ver == 4) OS2.version2(data, offset, obj);
        else if (ver == 5) OS2.version5(data, offset, obj);
        else throw "unknown OS/2 table version: " + ver;

        return obj;
    },

    version0: function (data, offset, obj) {
        var bin = Typr["B"];
        obj["xAvgCharWidth"] = bin.readShort(data, offset); offset += 2;
        obj["usWeightClass"] = bin.readUshort(data, offset); offset += 2;
        obj["usWidthClass"] = bin.readUshort(data, offset); offset += 2;
        obj["fsType"] = bin.readUshort(data, offset); offset += 2;
        obj["ySubscriptXSize"] = bin.readShort(data, offset); offset += 2;
        obj["ySubscriptYSize"] = bin.readShort(data, offset); offset += 2;
        obj["ySubscriptXOffset"] = bin.readShort(data, offset); offset += 2;
        obj["ySubscriptYOffset"] = bin.readShort(data, offset); offset += 2;
        obj["ySuperscriptXSize"] = bin.readShort(data, offset); offset += 2;
        obj["ySuperscriptYSize"] = bin.readShort(data, offset); offset += 2;
        obj["ySuperscriptXOffset"] = bin.readShort(data, offset); offset += 2;
        obj["ySuperscriptYOffset"] = bin.readShort(data, offset); offset += 2;
        obj["yStrikeoutSize"] = bin.readShort(data, offset); offset += 2;
        obj["yStrikeoutPosition"] = bin.readShort(data, offset); offset += 2;
        obj["sFamilyClass"] = bin.readShort(data, offset); offset += 2;
        obj["panose"] = bin.readBytes(data, offset, 10); offset += 10;
        obj["ulUnicodeRange1"] = bin.readUint(data, offset); offset += 4;
        obj["ulUnicodeRange2"] = bin.readUint(data, offset); offset += 4;
        obj["ulUnicodeRange3"] = bin.readUint(data, offset); offset += 4;
        obj["ulUnicodeRange4"] = bin.readUint(data, offset); offset += 4;
        obj["achVendID"] = bin.readASCII(data, offset, 4); offset += 4;
        obj["fsSelection"] = bin.readUshort(data, offset); offset += 2;
        obj["usFirstCharIndex"] = bin.readUshort(data, offset); offset += 2;
        obj["usLastCharIndex"] = bin.readUshort(data, offset); offset += 2;
        obj["sTypoAscender"] = bin.readShort(data, offset); offset += 2;
        obj["sTypoDescender"] = bin.readShort(data, offset); offset += 2;
        obj["sTypoLineGap"] = bin.readShort(data, offset); offset += 2;
        obj["usWinAscent"] = bin.readUshort(data, offset); offset += 2;
        obj["usWinDescent"] = bin.readUshort(data, offset); offset += 2;
        return offset;
    },

    version1: function (data, offset, obj) {
        var bin = Typr["B"];
        offset = Typr["T"].OS2.version0(data, offset, obj);

        obj["ulCodePageRange1"] = bin.readUint(data, offset); offset += 4;
        obj["ulCodePageRange2"] = bin.readUint(data, offset); offset += 4;
        return offset;
    },

    version2: function (data, offset, obj) {
        var bin = Typr["B"], rU = bin.readUshort;
        offset = Typr["T"].OS2.version1(data, offset, obj);

        obj["sxHeight"] = bin.readShort(data, offset); offset += 2;
        obj["sCapHeight"] = bin.readShort(data, offset); offset += 2;
        obj["usDefault"] = rU(data, offset); offset += 2;
        obj["usBreak"] = rU(data, offset); offset += 2;
        obj["usMaxContext"] = rU(data, offset); offset += 2;
        return offset;
    },

    version5: function (data, offset, obj) {
        var rU = Typr["B"].readUshort;
        offset = Typr["T"].OS2.version2(data, offset, obj);

        obj["usLowerOpticalPointSize"] = rU(data, offset); offset += 2;
        obj["usUpperOpticalPointSize"] = rU(data, offset); offset += 2;
        return offset;
    }
}

Typr["T"].post = {
    parseTab: function (data, offset, length) {
        var bin = Typr["B"];
        var obj = {};

        obj["version"] = bin.readFixed(data, offset); offset += 4;
        obj["italicAngle"] = bin.readFixed(data, offset); offset += 4;
        obj["underlinePosition"] = bin.readShort(data, offset); offset += 2;
        obj["underlineThickness"] = bin.readShort(data, offset); offset += 2;

        return obj;
    }
};
Typr["T"].SVG = {
    parseTab: function (data, offset, length) {
        var bin = Typr["B"];
        var obj = { entries: [] };

        var offset0 = offset;

        var tableVersion = bin.readUshort(data, offset); offset += 2;
        var svgDocIndexOffset = bin.readUint(data, offset); offset += 4;
        var reserved = bin.readUint(data, offset); offset += 4;

        offset = svgDocIndexOffset + offset0;

        var numEntries = bin.readUshort(data, offset); offset += 2;

        for (var i = 0; i < numEntries; i++) {
            var startGlyphID = bin.readUshort(data, offset); offset += 2;
            var endGlyphID = bin.readUshort(data, offset); offset += 2;
            var svgDocOffset = bin.readUint(data, offset); offset += 4;
            var svgDocLength = bin.readUint(data, offset); offset += 4;

            var sbuf = new Uint8Array(data.buffer, offset0 + svgDocOffset + svgDocIndexOffset, svgDocLength);
            var svg = bin.readUTF8(sbuf, 0, sbuf.length);

            for (var f = startGlyphID; f <= endGlyphID; f++) {
                obj.entries[f] = svg;
            }
        }
        return obj;
    }
};



Typr["U"] = {
    "shape": function (font, str, ltr) {

        var getGlyphPosition = function (font, gls, i1, ltr) {
            var g1 = gls[i1], g2 = gls[i1 + 1], kern = font["kern"];
            if (kern) {
                var ind1 = kern.glyph1.indexOf(g1);
                if (ind1 != -1) {
                    var ind2 = kern.rval[ind1].glyph2.indexOf(g2);
                    if (ind2 != -1) return [0, 0, kern.rval[ind1].vals[ind2], 0];
                }
            }
            //console.log("no kern");
            return [0, 0, 0, 0];
        }


        var gls = [];
        for (var i = 0; i < str.length; i++) {
            var cc = str.codePointAt(i); if (cc > 0xffff) i++;
            gls.push(Typr["U"]["codeToGlyph"](font, cc));
        }
        var shape = [];
        var x = 0, y = 0;

        for (var i = 0; i < gls.length; i++) {
            var padj = getGlyphPosition(font, gls, i, ltr);
            var gid = gls[i];
            var ax = font["hmtx"].aWidth[gid] + padj[2];
            shape.push({ "g": gid, "cl": i, "dx": 0, "dy": 0, "ax": ax, "ay": 0 });
            x += ax;
        }
        return shape;
    },

    "shapeToPath": function (font, shape, clr) {
        var tpath = { cmds: [], crds: [] };
        var x = 0, y = 0;

        for (var i = 0; i < shape.length; i++) {
            var it = shape[i]
            var path = Typr["U"]["glyphToPath"](font, it["g"]), crds = path["crds"];
            for (var j = 0; j < crds.length; j += 2) {
                tpath.crds.push(crds[j] + x + it["dx"]);
                tpath.crds.push(crds[j + 1] + y + it["dy"]);
            }
            if (clr) tpath.cmds.push(clr);
            for (var j = 0; j < path["cmds"].length; j++) tpath.cmds.push(path["cmds"][j]);
            var clen = tpath.cmds.length;
            if (clr) if (clen != 0 && tpath.cmds[clen - 1] != "X") tpath.cmds.push("X");  // SVG fonts might contain "X". Then, nothing would stroke non-SVG glyphs.

            x += it["ax"]; y += it["ay"];
        }
        return { "cmds": tpath.cmds, "crds": tpath.crds };
    },

    "codeToGlyph": function (font, code) {
        var cmap = font["cmap"];
        //console.log(cmap);
        // "p3e10" for NotoEmoji-Regular.ttf
        var tind = -1, pps = ["p3e10", "p0e4", "p3e1", "p1e0", "p0e3", "p0e1"/*,"p3e3"*/];
        for (var i = 0; i < pps.length; i++) if (cmap.ids[pps[i]] != null) { tind = cmap.ids[pps[i]]; break; }
        if (tind == -1) throw "no familiar platform and encoding!";


        // find the greatest index with a value <=v
        var arrSearch = function (arr, k, v) {
            var l = 0, r = Math.floor(arr.length / k);
            while (l + 1 != r) { var mid = l + ((r - l) >>> 1); if (arr[mid * k] <= v) l = mid; else r = mid; }
            return l * k;
        }

        var tab = cmap.tables[tind], fmt = tab.format, gid = -1;  //console.log(fmt); throw "e";

        if (fmt == 0) {
            if (code >= tab.map.length) gid = 0;
            else gid = tab.map[code];
        }
        /*else if(fmt==2) {
            var data=font["_data"], off = cmap.off+tab.off+6, bin=Typr["B"];
            var shKey = bin.readUshort(data,off + 2*(code>>>8));
            var shInd = off + 256*2 + shKey*8;
        	
            var firstCode = bin.readUshort(data,shInd);
            var entryCount= bin.readUshort(data,shInd+2);
            var idDelta   = bin.readShort (data,shInd+4);
            var idRangeOffset = bin.readUshort(data,shInd+6);
        	
            if(firstCode<=code && code<=firstCode+entryCount) {
                // not completely correct
                gid = bin.readUshort(data, shInd+6+idRangeOffset + (code&255)*2);
            }
            else gid=0;
            //if(code>256) console.log(code,(code>>>8),shKey,firstCode,entryCount,idDelta,idRangeOffset);
        	
            //throw "e";
            //console.log(tab,  bin.readUshort(data,off));
            //throw "e";
        }*/
        else if (fmt == 4) {
            var sind = -1, ec = tab.endCount;
            if (code > ec[ec.length - 1]) sind = -1;
            else {
                // smallest index with code <= value
                sind = arrSearch(ec, 1, code);
                if (ec[sind] < code) sind++;
            }
            if (sind == -1) gid = 0;
            else if (code < tab.startCount[sind]) gid = 0;
            else {
                var gli = 0;
                if (tab.idRangeOffset[sind] != 0) gli = tab.glyphIdArray[(code - tab.startCount[sind]) + (tab.idRangeOffset[sind] >> 1) - (tab.idRangeOffset.length - sind)];
                else gli = code + tab.idDelta[sind];
                gid = (gli & 0xFFFF);
            }
        }
        else if (fmt == 6) {
            var off = code - tab.firstCode, arr = tab.glyphIdArray;
            if (off < 0 || off >= arr.length) gid = 0;
            else gid = arr[off];
        }
        else if (fmt == 12) {
            var grp = tab.groups;  //console.log(grp);  throw "e";

            if (code > grp[grp.length - 2]) gid = 0;
            else {
                var i = arrSearch(grp, 3, code);
                if (grp[i] <= code && code <= grp[i + 1]) { gid = grp[i + 2] + (code - grp[i]); }
                if (gid == -1) gid = 0;
            }
        }
        else throw "unknown cmap table format " + tab.format;

        //*
        var SVG = font["SVG "], loca = font["loca"];
        // if the font claims to have a Glyph for a character, but the glyph is empty, and the character is not "white", it is a lie!
        if (gid != 0 && font["CFF "] == null && (SVG == null || SVG.entries[gid] == null) && loca[gid] == loca[gid + 1]  // loca not present in CFF or SVG fonts
            && [0x9, 0xa, 0xb, 0xc, 0xd, 0x20, 0x85, 0xa0, 0x1680, 0x2028, 0x2029, 0x202f, 0x3000,
                0x180e, 0x200b, 0x200c, 0x200d, 0x2060, 0xfeff].indexOf(code) == -1 && !(0x2000 <= code && code <= 0x200a)) gid = 0;
        //*/

        return gid;
    },

    "glyphToPath": function (font, gid) {
        var path = { cmds: [], crds: [] };
        var SVG = font["SVG "], CFF = font["CFF "];
        var U = Typr["U"];
        if (SVG && SVG.entries[gid]) {
            var p = SVG.entries[gid];
            if (p != null) {
                if (typeof p == "string") { p = U["SVG"].toPath(p); SVG.entries[gid] = p; }
                path = p;
            }
        }
        else if (CFF) {
            var pdct = CFF["Private"];
            var state = { x: 0, y: 0, stack: [], nStems: 0, haveWidth: false, width: pdct ? pdct["defaultWidthX"] : 0, open: false };
            if (CFF["ROS"]) {
                var gi = 0;
                while (CFF["FDSelect"][gi + 2] <= gid) gi += 2;
                pdct = CFF["FDArray"][CFF["FDSelect"][gi + 1]]["Private"];
            }
            U["_drawCFF"](CFF["CharStrings"][gid], state, CFF, pdct, path);
        }
        else if (font["glyf"]) { U["_drawGlyf"](gid, font, path); }
        return { "cmds": path.cmds, "crds": path.crds };
    },

    "_drawGlyf": function (gid, font, path) {
        var gl = font["glyf"][gid];
        if (gl == null) gl = font["glyf"][gid] = Typr["T"].glyf._parseGlyf(font, gid);
        if (gl != null) {
            if (gl.noc > -1) Typr["U"]["_simpleGlyph"](gl, path);
            else Typr["U"]["_compoGlyph"](gl, font, path);
        }
    },
    "_simpleGlyph": function (gl, p) {
        var P = Typr["U"]["P"];
        for (var c = 0; c < gl.noc; c++) {
            var i0 = (c == 0) ? 0 : (gl.endPts[c - 1] + 1);
            var il = gl.endPts[c];

            for (var i = i0; i <= il; i++) {
                var pr = (i == i0) ? il : (i - 1);
                var nx = (i == il) ? i0 : (i + 1);
                var onCurve = gl.flags[i] & 1;
                var prOnCurve = gl.flags[pr] & 1;
                var nxOnCurve = gl.flags[nx] & 1;

                var x = gl.xs[i], y = gl.ys[i];

                if (i == i0) {
                    if (onCurve) {
                        if (prOnCurve) P.MoveTo(p, gl.xs[pr], gl.ys[pr]);
                        else { P.MoveTo(p, x, y); continue;  /*  will do CurveTo at il  */ }
                    }
                    else {
                        if (prOnCurve) P.MoveTo(p, gl.xs[pr], gl.ys[pr]);
                        else P.MoveTo(p, Math.floor((gl.xs[pr] + x) * 0.5), Math.floor((gl.ys[pr] + y) * 0.5));
                    }
                }
                if (onCurve) {
                    if (prOnCurve) P.LineTo(p, x, y);
                }
                else {
                    if (nxOnCurve) P.qCurveTo(p, x, y, gl.xs[nx], gl.ys[nx]);
                    else P.qCurveTo(p, x, y, Math.floor((x + gl.xs[nx]) * 0.5), Math.floor((y + gl.ys[nx]) * 0.5));
                }
            }
            P.ClosePath(p);
        }
    },
    "_compoGlyph": function (gl, font, p) {
        for (var j = 0; j < gl.parts.length; j++) {
            var path = { cmds: [], crds: [] };
            var prt = gl.parts[j];
            Typr["U"]["_drawGlyf"](prt.glyphIndex, font, path);

            var m = prt.m;
            for (var i = 0; i < path.crds.length; i += 2) {
                var x = path.crds[i], y = path.crds[i + 1];
                p.crds.push(x * m.a + y * m.b + m.tx);
                p.crds.push(x * m.c + y * m.d + m.ty);
            }
            for (var i = 0; i < path.cmds.length; i++) p.cmds.push(path.cmds[i]);
        }
    },

    "pathToSVG": function (path, prec) {
        var cmds = path["cmds"], crds = path["crds"];
        if (prec == null) prec = 5;
        var out = [], co = 0, lmap = { "M": 2, "L": 2, "Q": 4, "C": 6 };
        for (var i = 0; i < cmds.length; i++) {
            var cmd = cmds[i], cn = co + (lmap[cmd] ? lmap[cmd] : 0);
            out.push(cmd);
            while (co < cn) { var c = crds[co++]; out.push(parseFloat(c.toFixed(prec)) + (co == cn ? "" : " ")); }
        }
        return out.join("");
    },
    "SVGToPath": function (d) {
        var pth = { cmds: [], crds: [] };
        Typr["U"]["SVG"].svgToPath(d, pth);
        return { "cmds": pth.cmds, "crds": pth.crds };
    },

    "pathToContext": function (path, ctx) {
        var c = 0, cmds = path["cmds"], crds = path["crds"];

        for (var j = 0; j < cmds.length; j++) {
            var cmd = cmds[j];
            if (cmd == "M") {
                ctx.moveTo(crds[c], crds[c + 1]);
                c += 2;
            }
            else if (cmd == "L") {
                ctx.lineTo(crds[c], crds[c + 1]);
                c += 2;
            }
            else if (cmd == "C") {
                ctx.bezierCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3], crds[c + 4], crds[c + 5]);
                c += 6;
            }
            else if (cmd == "Q") {
                ctx.quadraticCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3]);
                c += 4;
            }
            else if (cmd.charAt(0) == "#") {
                ctx.beginPath();
                ctx.fillStyle = cmd;
            }
            else if (cmd == "Z") {
                ctx.closePath();
            }
            else if (cmd == "X") {
                ctx.fill();
            }
        }
    },

    "P": {
        MoveTo: function (p, x, y) { p.cmds.push("M"); p.crds.push(x, y); },
        LineTo: function (p, x, y) { p.cmds.push("L"); p.crds.push(x, y); },
        CurveTo: function (p, a, b, c, d, e, f) { p.cmds.push("C"); p.crds.push(a, b, c, d, e, f); },
        qCurveTo: function (p, a, b, c, d) { p.cmds.push("Q"); p.crds.push(a, b, c, d); },
        ClosePath: function (p) { p.cmds.push("Z"); }
    },

    "_drawCFF": function (cmds, state, font, pdct, p) {
        var stack = state.stack;
        var nStems = state.nStems, haveWidth = state.haveWidth, width = state.width, open = state.open;
        var i = 0;
        var x = state.x, y = state.y, c1x = 0, c1y = 0, c2x = 0, c2y = 0, c3x = 0, c3y = 0, c4x = 0, c4y = 0, jpx = 0, jpy = 0;
        var CFF = Typr["T"].CFF, P = Typr["U"]["P"];

        var nominalWidthX = pdct["nominalWidthX"];
        var o = { val: 0, size: 0 };
        //console.log(cmds);
        while (i < cmds.length) {
            CFF.getCharString(cmds, i, o);
            var v = o.val;
            i += o.size;

            if (false) { }
            else if (v == "o1" || v == "o18")  //  hstem || hstemhm
            {
                var hasWidthArg;

                // The number of stem operators on the stack is always even.
                // If the value is uneven, that means a width is specified.
                hasWidthArg = stack.length % 2 !== 0;
                if (hasWidthArg && !haveWidth) {
                    width = stack.shift() + nominalWidthX;
                }

                nStems += stack.length >> 1;
                stack.length = 0;
                haveWidth = true;
            }
            else if (v == "o3" || v == "o23")  // vstem || vstemhm
            {
                var hasWidthArg;

                // The number of stem operators on the stack is always even.
                // If the value is uneven, that means a width is specified.
                hasWidthArg = stack.length % 2 !== 0;
                if (hasWidthArg && !haveWidth) {
                    width = stack.shift() + nominalWidthX;
                }

                nStems += stack.length >> 1;
                stack.length = 0;
                haveWidth = true;
            }
            else if (v == "o4") {
                if (stack.length > 1 && !haveWidth) {
                    width = stack.shift() + nominalWidthX;
                    haveWidth = true;
                }
                if (open) P.ClosePath(p);

                y += stack.pop();
                P.MoveTo(p, x, y); open = true;
            }
            else if (v == "o5") {
                while (stack.length > 0) {
                    x += stack.shift();
                    y += stack.shift();
                    P.LineTo(p, x, y);
                }
            }
            else if (v == "o6" || v == "o7")  // hlineto || vlineto
            {
                var count = stack.length;
                var isX = (v == "o6");

                for (var j = 0; j < count; j++) {
                    var sval = stack.shift();

                    if (isX) x += sval; else y += sval;
                    isX = !isX;
                    P.LineTo(p, x, y);
                }
            }
            else if (v == "o8" || v == "o24")	// rrcurveto || rcurveline
            {
                var count = stack.length;
                var index = 0;
                while (index + 6 <= count) {
                    c1x = x + stack.shift();
                    c1y = y + stack.shift();
                    c2x = c1x + stack.shift();
                    c2y = c1y + stack.shift();
                    x = c2x + stack.shift();
                    y = c2y + stack.shift();
                    P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
                    index += 6;
                }
                if (v == "o24") {
                    x += stack.shift();
                    y += stack.shift();
                    P.LineTo(p, x, y);
                }
            }
            else if (v == "o11") break;
            else if (v == "o1234" || v == "o1235" || v == "o1236" || v == "o1237")//if((v+"").slice(0,3)=="o12")
            {
                if (v == "o1234") {
                    c1x = x + stack.shift();    // dx1
                    c1y = y;                      // dy1
                    c2x = c1x + stack.shift();    // dx2
                    c2y = c1y + stack.shift();    // dy2
                    jpx = c2x + stack.shift();    // dx3
                    jpy = c2y;                    // dy3
                    c3x = jpx + stack.shift();    // dx4
                    c3y = c2y;                    // dy4
                    c4x = c3x + stack.shift();    // dx5
                    c4y = y;                      // dy5
                    x = c4x + stack.shift();      // dx6
                    P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
                    P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);

                }
                if (v == "o1235") {
                    c1x = x + stack.shift();    // dx1
                    c1y = y + stack.shift();    // dy1
                    c2x = c1x + stack.shift();    // dx2
                    c2y = c1y + stack.shift();    // dy2
                    jpx = c2x + stack.shift();    // dx3
                    jpy = c2y + stack.shift();    // dy3
                    c3x = jpx + stack.shift();    // dx4
                    c3y = jpy + stack.shift();    // dy4
                    c4x = c3x + stack.shift();    // dx5
                    c4y = c3y + stack.shift();    // dy5
                    x = c4x + stack.shift();      // dx6
                    y = c4y + stack.shift();      // dy6
                    stack.shift();                // flex depth
                    P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
                    P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
                }
                if (v == "o1236") {
                    c1x = x + stack.shift();    // dx1
                    c1y = y + stack.shift();    // dy1
                    c2x = c1x + stack.shift();    // dx2
                    c2y = c1y + stack.shift();    // dy2
                    jpx = c2x + stack.shift();    // dx3
                    jpy = c2y;                    // dy3
                    c3x = jpx + stack.shift();    // dx4
                    c3y = c2y;                    // dy4
                    c4x = c3x + stack.shift();    // dx5
                    c4y = c3y + stack.shift();    // dy5
                    x = c4x + stack.shift();      // dx6
                    P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
                    P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
                }
                if (v == "o1237") {
                    c1x = x + stack.shift();    // dx1
                    c1y = y + stack.shift();    // dy1
                    c2x = c1x + stack.shift();    // dx2
                    c2y = c1y + stack.shift();    // dy2
                    jpx = c2x + stack.shift();    // dx3
                    jpy = c2y + stack.shift();    // dy3
                    c3x = jpx + stack.shift();    // dx4
                    c3y = jpy + stack.shift();    // dy4
                    c4x = c3x + stack.shift();    // dx5
                    c4y = c3y + stack.shift();    // dy5
                    if (Math.abs(c4x - x) > Math.abs(c4y - y)) {
                        x = c4x + stack.shift();
                    } else {
                        y = c4y + stack.shift();
                    }
                    P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
                    P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
                }
            }
            else if (v == "o14") {
                if (stack.length > 0 && !haveWidth) {
                    width = stack.shift() + font["nominalWidthX"];
                    haveWidth = true;
                }
                if (stack.length == 4) // seac = standard encoding accented character
                {

                    var asb = 0;
                    var adx = stack.shift();
                    var ady = stack.shift();
                    var bchar = stack.shift();
                    var achar = stack.shift();


                    var bind = CFF.glyphBySE(font, bchar);
                    var aind = CFF.glyphBySE(font, achar);

                    //console.log(bchar, bind);
                    //console.log(achar, aind);
                    //state.x=x; state.y=y; state.nStems=nStems; state.haveWidth=haveWidth; state.width=width;  state.open=open;

                    Typr["U"]["_drawCFF"](font["CharStrings"][bind], state, font, pdct, p);
                    state.x = adx; state.y = ady;
                    Typr["U"]["_drawCFF"](font["CharStrings"][aind], state, font, pdct, p);

                    //x=state.x; y=state.y; nStems=state.nStems; haveWidth=state.haveWidth; width=state.width;  open=state.open;
                }
                if (open) { P.ClosePath(p); open = false; }
            }
            else if (v == "o19" || v == "o20") {
                var hasWidthArg;

                // The number of stem operators on the stack is always even.
                // If the value is uneven, that means a width is specified.
                hasWidthArg = stack.length % 2 !== 0;
                if (hasWidthArg && !haveWidth) {
                    width = stack.shift() + nominalWidthX;
                }

                nStems += stack.length >> 1;
                stack.length = 0;
                haveWidth = true;

                i += (nStems + 7) >> 3;
            }

            else if (v == "o21") {
                if (stack.length > 2 && !haveWidth) {
                    width = stack.shift() + nominalWidthX;
                    haveWidth = true;
                }

                y += stack.pop();
                x += stack.pop();

                if (open) P.ClosePath(p);
                P.MoveTo(p, x, y); open = true;
            }
            else if (v == "o22") {
                if (stack.length > 1 && !haveWidth) {
                    width = stack.shift() + nominalWidthX;
                    haveWidth = true;
                }

                x += stack.pop();

                if (open) P.ClosePath(p);
                P.MoveTo(p, x, y); open = true;
            }
            else if (v == "o25") {
                while (stack.length > 6) {
                    x += stack.shift();
                    y += stack.shift();
                    P.LineTo(p, x, y);
                }

                c1x = x + stack.shift();
                c1y = y + stack.shift();
                c2x = c1x + stack.shift();
                c2y = c1y + stack.shift();
                x = c2x + stack.shift();
                y = c2y + stack.shift();
                P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
            }
            else if (v == "o26") {
                if (stack.length % 2) {
                    x += stack.shift();
                }

                while (stack.length > 0) {
                    c1x = x;
                    c1y = y + stack.shift();
                    c2x = c1x + stack.shift();
                    c2y = c1y + stack.shift();
                    x = c2x;
                    y = c2y + stack.shift();
                    P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
                }

            }
            else if (v == "o27") {
                if (stack.length % 2) {
                    y += stack.shift();
                }

                while (stack.length > 0) {
                    c1x = x + stack.shift();
                    c1y = y;
                    c2x = c1x + stack.shift();
                    c2y = c1y + stack.shift();
                    x = c2x + stack.shift();
                    y = c2y;
                    P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
                }
            }
            else if (v == "o10" || v == "o29")	// callsubr || callgsubr
            {
                var obj = (v == "o10" ? pdct : font);
                if (stack.length == 0) { console.log("error: empty stack"); }
                else {
                    var ind = stack.pop();
                    var subr = obj["Subrs"][ind + obj["Bias"]];
                    state.x = x; state.y = y; state.nStems = nStems; state.haveWidth = haveWidth; state.width = width; state.open = open;
                    Typr["U"]["_drawCFF"](subr, state, font, pdct, p);
                    x = state.x; y = state.y; nStems = state.nStems; haveWidth = state.haveWidth; width = state.width; open = state.open;
                }
            }
            else if (v == "o30" || v == "o31")   // vhcurveto || hvcurveto
            {
                var count, count1 = stack.length;
                var index = 0;
                var alternate = v == "o31";

                count = count1 & ~2;
                index += count1 - count;

                while (index < count) {
                    if (alternate) {
                        c1x = x + stack.shift();
                        c1y = y;
                        c2x = c1x + stack.shift();
                        c2y = c1y + stack.shift();
                        y = c2y + stack.shift();
                        if (count - index == 5) { x = c2x + stack.shift(); index++; }
                        else x = c2x;
                        alternate = false;
                    }
                    else {
                        c1x = x;
                        c1y = y + stack.shift();
                        c2x = c1x + stack.shift();
                        c2y = c1y + stack.shift();
                        x = c2x + stack.shift();
                        if (count - index == 5) { y = c2y + stack.shift(); index++; }
                        else y = c2y;
                        alternate = true;
                    }
                    P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
                    index += 4;
                }
            }

            else if ((v + "").charAt(0) == "o") { console.log("Unknown operation: " + v, cmds); throw v; }
            else stack.push(v);
        }
        //console.log(cmds);
        state.x = x; state.y = y; state.nStems = nStems; state.haveWidth = haveWidth; state.width = width; state.open = open;
    },


    "SVG": function () {
        var M = {
            getScale: function (m) { return Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2])); },
            translate: function (m, x, y) { M.concat(m, [1, 0, 0, 1, x, y]); },
            rotate: function (m, a) { M.concat(m, [Math.cos(a), -Math.sin(a), Math.sin(a), Math.cos(a), 0, 0]); },
            scale: function (m, x, y) { M.concat(m, [x, 0, 0, y, 0, 0]); },
            concat: function (m, w) {
                var a = m[0], b = m[1], c = m[2], d = m[3], tx = m[4], ty = m[5];
                m[0] = (a * w[0]) + (b * w[2]); m[1] = (a * w[1]) + (b * w[3]);
                m[2] = (c * w[0]) + (d * w[2]); m[3] = (c * w[1]) + (d * w[3]);
                m[4] = (tx * w[0]) + (ty * w[2]) + w[4]; m[5] = (tx * w[1]) + (ty * w[3]) + w[5];
            },
            invert: function (m) {
                var a = m[0], b = m[1], c = m[2], d = m[3], tx = m[4], ty = m[5], adbc = a * d - b * c;
                m[0] = d / adbc; m[1] = -b / adbc; m[2] = -c / adbc; m[3] = a / adbc;
                m[4] = (c * ty - d * tx) / adbc; m[5] = (b * tx - a * ty) / adbc;
            },
            multPoint: function (m, p) { var x = p[0], y = p[1]; return [x * m[0] + y * m[2] + m[4], x * m[1] + y * m[3] + m[5]]; },
            multArray: function (m, a) { for (var i = 0; i < a.length; i += 2) { var x = a[i], y = a[i + 1]; a[i] = x * m[0] + y * m[2] + m[4]; a[i + 1] = x * m[1] + y * m[3] + m[5]; } }
        }

        function _bracketSplit(str, lbr, rbr) {
            var out = [], pos = 0, ci = 0, lvl = 0;
            while (true) {  //throw "e";
                var li = str.indexOf(lbr, ci);
                var ri = str.indexOf(rbr, ci);
                if (li == -1 && ri == -1) break;
                if (ri == -1 || (li != -1 && li < ri)) {
                    if (lvl == 0) { out.push(str.slice(pos, li).trim()); pos = li + 1; }
                    lvl++; ci = li + 1;
                }
                else if (li == -1 || (ri != -1 && ri < li)) {
                    lvl--;
                    if (lvl == 0) { out.push(str.slice(pos, ri).trim()); pos = ri + 1; }
                    ci = ri + 1;
                }
            }
            return out;
        }
        //"cssMap": 
        function cssMap(str) {
            var pts = _bracketSplit(str, "{", "}");
            var css = {};
            for (var i = 0; i < pts.length; i += 2) {
                var cn = pts[i].split(",");
                for (var j = 0; j < cn.length; j++) {
                    var cnj = cn[j].trim(); if (css[cnj] == null) css[cnj] = "";
                    css[cnj] += pts[i + 1];
                }
            }
            return css;
        }
        //"readTrnf" 
        function readTrnf(trna) {
            var pts = _bracketSplit(trna, "(", ")");
            var m = [1, 0, 0, 1, 0, 0];
            for (var i = 0; i < pts.length; i += 2) { var om = m; m = _readTrnsAttr(pts[i], pts[i + 1]); M.concat(m, om); }
            return m;
        }

        function _readTrnsAttr(fnc, vls) {
            //console.log(vls);
            //vls = vls.replace(/\-/g, " -").trim();
            var m = [1, 0, 0, 1, 0, 0], gotSep = true;
            for (var i = 0; i < vls.length; i++) {	// matrix(.99915 0 0 .99915.418.552)   matrix(1 0 0-.9474-22.535 271.03)
                var ch = vls.charAt(i);
                if (ch == "," || ch == " ") gotSep = true;
                else if (ch == ".") {
                    if (!gotSep) { vls = vls.slice(0, i) + "," + vls.slice(i); i++; } gotSep = false;
                }
                else if (ch == "-" && i > 0 && vls[i - 1] != "e") { vls = vls.slice(0, i) + " " + vls.slice(i); i++; gotSep = true; }
            }

            vls = vls.split(/\s*[\s,]\s*/).map(parseFloat);
            if (false) { }
            else if (fnc == "translate") { if (vls.length == 1) M.translate(m, vls[0], 0); else M.translate(m, vls[0], vls[1]); }
            else if (fnc == "scale") { if (vls.length == 1) M.scale(m, vls[0], vls[0]); else M.scale(m, vls[0], vls[1]); }
            else if (fnc == "rotate") { var tx = 0, ty = 0; if (vls.length != 1) { tx = vls[1]; ty = vls[2]; } M.translate(m, -tx, -ty); M.rotate(m, -Math.PI * vls[0] / 180); M.translate(m, tx, ty); }
            else if (fnc == "matrix") m = vls;
            else console.log("unknown transform: ", fnc);
            return m;
        }

        function toPath(str) {
            var pth = { cmds: [], crds: [] };
            if (str == null) return pth;

            var prsr = new DOMParser();
            var doc = prsr["parseFromString"](str, "image/svg+xml");

            //var svg = doc.firstChild;  while(svg.tagName!="svg") svg = svg.nextSibling;
            var svg = doc.getElementsByTagName("svg")[0];
            var vb = svg.getAttribute("viewBox");
            if (vb) vb = vb.trim().split(" ").map(parseFloat); else vb = [0, 0, 1000, 1000];
            _toPath(svg.children, pth);
            for (var i = 0; i < pth.crds.length; i += 2) {
                var x = pth.crds[i], y = pth.crds[i + 1];
                x -= vb[0];
                y -= vb[1];
                y = -y;
                pth.crds[i] = x;
                pth.crds[i + 1] = y;
            }
            return pth;
        }

        function _toPath(nds, pth, fill) {
            for (var ni = 0; ni < nds.length; ni++) {
                var nd = nds[ni], tn = nd.tagName;
                var cfl = nd.getAttribute("fill"); if (cfl == null) cfl = fill;
                if (tn == "g") {
                    var tp = { crds: [], cmds: [] };
                    _toPath(nd.children, tp, cfl);
                    var trf = nd.getAttribute("transform");
                    if (trf) {
                        var m = readTrnf(trf);
                        M.multArray(m, tp.crds);
                    }
                    pth.crds = pth.crds.concat(tp.crds);
                    pth.cmds = pth.cmds.concat(tp.cmds);
                }
                else if (tn == "path" || tn == "circle" || tn == "ellipse") {
                    pth.cmds.push(cfl ? cfl : "#000000");
                    var d;
                    if (tn == "path") d = nd.getAttribute("d");  //console.log(d);
                    if (tn == "circle" || tn == "ellipse") {
                        var vls = [0, 0, 0, 0], nms = ["cx", "cy", "rx", "ry", "r"];
                        for (var i = 0; i < 5; i++) { var V = nd.getAttribute(nms[i]); if (V) { V = parseFloat(V); if (i < 4) vls[i] = V; else vls[2] = vls[3] = V; } }
                        var cx = vls[0], cy = vls[1], rx = vls[2], ry = vls[3];
                        d = ["M", cx - rx, cy, "a", rx, ry, 0, 1, 0, rx * 2, 0, "a", rx, ry, 0, 1, 0, -rx * 2, 0].join(" ");
                    }
                    svgToPath(d, pth); pth.cmds.push("X");
                }
                else if (tn == "defs") { }
                else console.log(tn, nd);
            }
        }

        function _tokens(d) {
            var ts = [], off = 0, rn = false, cn = "", pc = "";  // reading number, current number, prev char
            while (off < d.length) {
                var cc = d.charCodeAt(off), ch = d.charAt(off); off++;
                var isNum = (48 <= cc && cc <= 57) || ch == "." || ch == "-" || ch == "e" || ch == "E";

                if (rn) {
                    if ((ch == "-" && pc != "e") || (ch == "." && cn.indexOf(".") != -1)) { ts.push(parseFloat(cn)); cn = ch; }
                    else if (isNum) cn += ch;
                    else { ts.push(parseFloat(cn)); if (ch != "," && ch != " ") ts.push(ch); rn = false; }
                }
                else {
                    if (isNum) { cn = ch; rn = true; }
                    else if (ch != "," && ch != " ") ts.push(ch);
                }
                pc = ch;
            }
            if (rn) ts.push(parseFloat(cn));
            return ts;
        }

        function _reps(ts, off, ps) {
            var i = off;
            while (i < ts.length) { if ((typeof ts[i]) == "string") break; i += ps; }
            return (i - off) / ps;
        }

        function svgToPath(d, pth) {
            var ts = _tokens(d);
            var i = 0, x = 0, y = 0, ox = 0, oy = 0, oldo = pth.crds.length;
            var pc = { "M": 2, "L": 2, "H": 1, "V": 1, "T": 2, "S": 4, "A": 7, "Q": 4, "C": 6 };
            var cmds = pth.cmds, crds = pth.crds;

            while (i < ts.length) {
                var cmd = ts[i]; i++;
                var cmu = cmd.toUpperCase();

                if (cmu == "Z") { cmds.push("Z"); x = ox; y = oy; }
                else {
                    var ps = pc[cmu], reps = _reps(ts, i, ps);

                    for (var j = 0; j < reps; j++) {
                        // If a moveto is followed by multiple pairs of coordinates, the subsequent pairs are treated as implicit lineto commands.
                        if (j == 1 && cmu == "M") { cmd = (cmd == cmu) ? "L" : "l"; cmu = "L"; }

                        var xi = 0, yi = 0; if (cmd != cmu) { xi = x; yi = y; }

                        if (false) { }
                        else if (cmu == "M") { x = xi + ts[i++]; y = yi + ts[i++]; cmds.push("M"); crds.push(x, y); ox = x; oy = y; }
                        else if (cmu == "L") { x = xi + ts[i++]; y = yi + ts[i++]; cmds.push("L"); crds.push(x, y); }
                        else if (cmu == "H") { x = xi + ts[i++]; cmds.push("L"); crds.push(x, y); }
                        else if (cmu == "V") { y = yi + ts[i++]; cmds.push("L"); crds.push(x, y); }
                        else if (cmu == "Q") {
                            var x1 = xi + ts[i++], y1 = yi + ts[i++], x2 = xi + ts[i++], y2 = yi + ts[i++];
                            cmds.push("Q"); crds.push(x1, y1, x2, y2); x = x2; y = y2;
                        }
                        else if (cmu == "T") {
                            var co = Math.max(crds.length - 2, oldo);
                            var x1 = x + x - crds[co], y1 = y + y - crds[co + 1];
                            var x2 = xi + ts[i++], y2 = yi + ts[i++];
                            cmds.push("Q"); crds.push(x1, y1, x2, y2); x = x2; y = y2;
                        }
                        else if (cmu == "C") {
                            var x1 = xi + ts[i++], y1 = yi + ts[i++], x2 = xi + ts[i++], y2 = yi + ts[i++], x3 = xi + ts[i++], y3 = yi + ts[i++];
                            cmds.push("C"); crds.push(x1, y1, x2, y2, x3, y3); x = x3; y = y3;
                        }
                        else if (cmu == "S") {
                            var co = Math.max(crds.length - (cmds[cmds.length - 1] == "C" ? 4 : 2), oldo);
                            var x1 = x + x - crds[co], y1 = y + y - crds[co + 1];
                            var x2 = xi + ts[i++], y2 = yi + ts[i++], x3 = xi + ts[i++], y3 = yi + ts[i++];
                            cmds.push("C"); crds.push(x1, y1, x2, y2, x3, y3); x = x3; y = y3;
                        }
                        else if (cmu == "A") {  // convert SVG Arc to four cubic bézier segments "C"
                            var x1 = x, y1 = y;
                            var rx = ts[i++], ry = ts[i++];
                            var phi = ts[i++] * (Math.PI / 180), fA = ts[i++], fS = ts[i++];
                            var x2 = xi + ts[i++], y2 = yi + ts[i++];
                            if (x2 == x && y2 == y && rx == 0 && ry == 0) continue;

                            var hdx = (x1 - x2) / 2, hdy = (y1 - y2) / 2;
                            var cosP = Math.cos(phi), sinP = Math.sin(phi);
                            var x1A = cosP * hdx + sinP * hdy;
                            var y1A = -sinP * hdx + cosP * hdy;

                            var rxS = rx * rx, ryS = ry * ry;
                            var x1AS = x1A * x1A, y1AS = y1A * y1A;
                            var frc = (rxS * ryS - rxS * y1AS - ryS * x1AS) / (rxS * y1AS + ryS * x1AS);
                            var coef = (fA != fS ? 1 : -1) * Math.sqrt(Math.max(frc, 0));
                            var cxA = coef * (rx * y1A) / ry;
                            var cyA = -coef * (ry * x1A) / rx;

                            var cx = cosP * cxA - sinP * cyA + (x1 + x2) / 2;
                            var cy = sinP * cxA + cosP * cyA + (y1 + y2) / 2;

                            var angl = function (ux, uy, vx, vy) {
                                var lU = Math.sqrt(ux * ux + uy * uy), lV = Math.sqrt(vx * vx + vy * vy);
                                var num = (ux * vx + uy * vy) / (lU * lV);  //console.log(num, Math.acos(num));
                                return (ux * vy - uy * vx >= 0 ? 1 : -1) * Math.acos(Math.max(-1, Math.min(1, num)));
                            }

                            var vX = (x1A - cxA) / rx, vY = (y1A - cyA) / ry;
                            var theta1 = angl(1, 0, vX, vY);
                            var dtheta = angl(vX, vY, (-x1A - cxA) / rx, (-y1A - cyA) / ry);
                            dtheta = dtheta % (2 * Math.PI);

                            var arc = function (gst, x, y, r, a0, a1, neg) {
                                var rotate = function (m, a) {
                                    var si = Math.sin(a), co = Math.cos(a);
                                    var a = m[0], b = m[1], c = m[2], d = m[3];
                                    m[0] = (a * co) + (b * si); m[1] = (-a * si) + (b * co);
                                    m[2] = (c * co) + (d * si); m[3] = (-c * si) + (d * co);
                                }
                                var multArr = function (m, a) {
                                    for (var j = 0; j < a.length; j += 2) {
                                        var x = a[j], y = a[j + 1];
                                        a[j] = m[0] * x + m[2] * y + m[4];
                                        a[j + 1] = m[1] * x + m[3] * y + m[5];
                                    }
                                }
                                var concatA = function (a, b) { for (var j = 0; j < b.length; j++) a.push(b[j]); }
                                var concatP = function (p, r) { concatA(p.cmds, r.cmds); concatA(p.crds, r.crds); }
                                // circle from a0 counter-clock-wise to a1
                                if (neg) while (a1 > a0) a1 -= 2 * Math.PI;
                                else while (a1 < a0) a1 += 2 * Math.PI;
                                var th = (a1 - a0) / 4;

                                var x0 = Math.cos(th / 2), y0 = -Math.sin(th / 2);
                                var x1 = (4 - x0) / 3, y1 = y0 == 0 ? y0 : (1 - x0) * (3 - x0) / (3 * y0);
                                var x2 = x1, y2 = -y1;
                                var x3 = x0, y3 = -y0;

                                var ps = [x1, y1, x2, y2, x3, y3];

                                var pth = { cmds: ["C", "C", "C", "C"], crds: ps.slice(0) };
                                var rot = [1, 0, 0, 1, 0, 0]; rotate(rot, -th);
                                for (var j = 0; j < 3; j++) { multArr(rot, ps); concatA(pth.crds, ps); }

                                rotate(rot, -a0 + th / 2); rot[0] *= r; rot[1] *= r; rot[2] *= r; rot[3] *= r; rot[4] = x; rot[5] = y;
                                multArr(rot, pth.crds);
                                multArr(gst.ctm, pth.crds);
                                concatP(gst.pth, pth);
                            }

                            var gst = { pth: pth, ctm: [rx * cosP, rx * sinP, -ry * sinP, ry * cosP, cx, cy] };
                            arc(gst, 0, 0, 1, theta1, theta1 + dtheta, fS == 0);
                            x = x2; y = y2;
                        }
                        else console.log("Unknown SVG command " + cmd);
                    }
                }
            }
        };
        return { "cssMap": cssMap, "readTrnf": readTrnf, svgToPath: svgToPath, toPath: toPath };
    }(),




    "initHB": function (hurl, resp) {
        var codeLength = function (code) {
            var len = 0;
            if ((code & (0xffffffff - (1 << 7) + 1)) == 0) { len = 1; }
            else if ((code & (0xffffffff - (1 << 11) + 1)) == 0) { len = 2; }
            else if ((code & (0xffffffff - (1 << 16) + 1)) == 0) { len = 3; }
            else if ((code & (0xffffffff - (1 << 21) + 1)) == 0) { len = 4; }
            return len;
        }
        var te = new window["TextEncoder"]("utf8");

        fetch(hurl)
            .then(function (x) { return x["arrayBuffer"](); })
            .then(function (ab) { return WebAssembly["instantiate"](ab); })
            .then(function (res) {
                console.log("HB ready");
                var exp = res["instance"]["exports"], mem = exp["memory"];
                mem["grow"](700); // each page is 64kb in size
                var heapu8 = new Uint8Array(mem.buffer);
                var u32 = new Uint32Array(mem.buffer);
                var i32 = new Int32Array(mem.buffer);
                var __lastFnt, blob, blobPtr, face, font;

                Typr["U"]["shapeHB"] = (function () {

                    var toJson = function (ptr) {
                        var length = exp["hb_buffer_get_length"](ptr);
                        var result = [];
                        var iPtr32 = exp["hb_buffer_get_glyph_infos"](ptr, 0) >>> 2;
                        var pPtr32 = exp["hb_buffer_get_glyph_positions"](ptr, 0) >>> 2;
                        for (var i = 0; i < length; ++i) {
                            var a = iPtr32 + i * 5, b = pPtr32 + i * 5;
                            result.push({
                                "g": u32[a + 0],
                                "cl": u32[a + 2],
                                "ax": i32[b + 0],
                                "ay": i32[b + 1],
                                "dx": i32[b + 2],
                                "dy": i32[b + 3]
                            });
                        }
                        return result;
                    }
                    return function (fnt, str, ltr) {
                        var fdata = fnt["_data"], fn = fnt["name"]["postScriptName"];

                        if (__lastFnt != fn) {
                            if (blob != null) {
                                exp["hb_blob_destroy"](blob);
                                exp["free"](blobPtr);
                                exp["hb_face_destroy"](face);
                                exp["hb_font_destroy"](font);
                            }
                            blobPtr = exp["malloc"](fdata.byteLength); heapu8.set(fdata, blobPtr);
                            blob = exp["hb_blob_create"](blobPtr, fdata.byteLength, 2, 0, 0);
                            face = exp["hb_face_create"](blob, 0);
                            font = exp["hb_font_create"](face)
                            __lastFnt = fn;
                        }

                        var buffer = exp["hb_buffer_create"]();
                        var bytes = te["encode"](str);
                        var len = bytes.length, strp = exp["malloc"](len); heapu8.set(bytes, strp);
                        exp["hb_buffer_add_utf8"](buffer, strp, len, 0, len);
                        exp["free"](strp);

                        exp["hb_buffer_set_direction"](buffer, ltr ? 4 : 5);
                        exp["hb_buffer_guess_segment_properties"](buffer);
                        exp["hb_shape"](font, buffer, 0, 0);
                        var json = toJson(buffer)//buffer["json"]();
                        exp["hb_buffer_destroy"](buffer);

                        var arr = json.slice(0); if (!ltr) arr.reverse();
                        var ci = 0, bi = 0;  // character index, binary index
                        for (var i = 1; i < arr.length; i++) {
                            var gl = arr[i], cl = gl["cl"];
                            while (true) {
                                var cpt = str.codePointAt(ci), cln = codeLength(cpt);
                                if (bi + cln <= cl) { bi += cln; ci += cpt <= 0xffff ? 1 : 2; }
                                else break;
                            }
                            //while(bi+codeLength(str.charCodeAt(ci)) <=cl) {  bi+=codeLength(str.charCodeAt(ci));  ci++;  }
                            gl["cl"] = ci;
                        }
                        return json;
                    }
                }());
                resp();
            });
    }
}



export default Typr